using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Collections;
using Microsoft.SPOT;
using Gadgeteer;
using Gadgeteer.Modules;

namespace GadgeteerLibrary
{
    /// <summary>
    /// Handles the communication with REST-ful web services
    /// </summary>
    public class RESTClient
    {
        private Uri baseURI;
        private string apiKey;
        private const string RESULT = "\"Success\"";
        private Hashtable sensorTable = new Hashtable()
        {
            {SensorType.temperatureHumidity, "temperature"},
            {SensorType.light, "light"},
            {SensorType.pulse, "pulse"},
            {SensorType.gps, "gps"},
            {SensorType.accelerometer, "accelerometer"},
            {SensorType.potentiometer, "potentiometer"},
            {SensorType.barometer, "barometer"},
            {SensorType.color, "color"},
            {SensorType.compass, "compass"},
            {SensorType.current, "current"},
            {SensorType.distance, "distance"},
            {SensorType.gas, "gas"},
            {SensorType.gyro, "gyro"},
            {SensorType.moisture, "moisture"},
            {SensorType.thermocouple, "thermocouple"}
        };

        public Device Device { get; set; }
        public ArrayList Sensors { get; set; }

        /// <summary>
        /// Public constructor to initialise the instance
        /// </summary>
        /// <param name="apiKey">Unique API Key got from adding a device on the web site</param>
        /// <param name="device">Device information</param>
        /// <param name="sensors">An ArrayList of sensors which contain necessary information 
        /// under the same device</param>
        public RESTClient(string apiKey, Device device, ArrayList sensors)
        {
            this.apiKey = apiKey;
            this.Device = device;
            this.Sensors = sensors;
            // Base URL for the RESTful web service
            this.baseURI = new Uri("http://uclwebservicetest.cloudapp.net");
        }

        // Creates an HTTP request
        private HttpWebRequest CreateRequest(string suffixURI)
        {
            Uri serviceEndPoint = new Uri(baseURI.AbsoluteUri + suffixURI);
            HttpWebRequest request =
                (HttpWebRequest)WebRequest.Create(serviceEndPoint);

            // Set the APIKey as the Authorization header field to use for this request.
            request.Headers.Add("Authorization", "APIKey " + this.apiKey);
            request.ContentType = "application/json; charset=utf-8";
            return request;
        }

        // Gets the HTTP response based on the request and the content, typically for POST or PUT method
        private HttpWebResponse GetResponse(HttpWebRequest request, string jsonData)
        {
            using (StreamWriter requestWriter =
                new StreamWriter(request.GetRequestStream()))
            {
                requestWriter.Write(jsonData);
            }

            return (HttpWebResponse)request.GetResponse();
        }

        // Gets the HTTP response based on the request, typically for GET or DELETE method
        private HttpWebResponse GetResponse(HttpWebRequest request)
        {
            return (HttpWebResponse)request.GetResponse();
        }

        // Creates a json string for sensor registration
        private string RegistrationJSON()
        {
            string json = "{\"DeviceId\":" + this.Device.Id
                        + ",\"Sensors\":[";
            foreach (Sensor s in this.Sensors)
            {
                json += "{\"Interval_measure\":\"" + s.MeasureInterval
                        + "\",\"Interval_send\":\"" + s.SendingInterval
                        + "\",\"Make\":\"" + s.Make
                        + "\",\"Metadata\":\"ForLater\","
                        + "\"Name\":\"" + s.Name
                        + "\",\"Fault_tolerance\":" + s.FaultTolerance
                        + ",\"Type\":\"" + (string)(sensorTable[s.Type]) + "\"},";
            }
            json = json.Substring(0, json.Length - 1);
            json += "]}";
            return json;
        }

        /// <summary>
        /// Registers the sensors associated with the RESTClient instance
        /// </summary>
        /// <returns>Returns true if registration is successful, or false if failed</returns>
        public bool RegisterSensor()
        {
            string jsonData = RegistrationJSON();
            Debug.Print(jsonData);

            try
            {
                HttpWebRequest request
                    = CreateRequest("Services/device.svc/device");
                request.Method = "POST";
                request.ContentLength = jsonData.Length;

                using (HttpWebResponse response = GetResponse(request, jsonData))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            foreach (Sensor s in Sensors)
                            {
                                s.Id = response.GetResponseHeader(s.Name);
                            }

                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
        }

        /// <summary>
        /// Deletes the sensor from web service
        /// </summary>
        /// <param name="sensorName">Name of the existing and registered sensor</param>
        /// <returns>Returns true if the deletion is successful, or false if failed</returns>
        public bool DeleteSensor(string sensorName)
        //public string DeleteSensor(string sensorName)
        {
            try
            {
                HttpWebRequest request = CreateRequest(
                    "Services/device.svc/device/" + this.RetrieveID(sensorName));
                request.Method = "Delete";

                using (HttpWebResponse response = GetResponse(request))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            return true;
                            //return streamReader.ReadToEnd();
                        }
                        else
                        {
                            return false;
                            //return streamReader.ReadToEnd();
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
                //return we.Message;
            }
        }

        /// <summary>
        /// Sends a notification to the web service if the IP Address of a certain device is changed
        /// </summary>
        /// <param name="ipAddress">New IP Address of the device</param>
        /// <returns>Returns true if the notification is successful, or false if failed</returns>
        public bool NotifyIPAddress(string ipAddress)
        {
            try
            {
                HttpWebRequest request = CreateRequest(
                    "Services/device.svc/device/changeip?deviceid=" + Device.Id
                    + "&ipaddress=" + ipAddress);
                request.Method = "GET";
                request.ContentLength = 0;

                using (HttpWebResponse response = GetResponse(request))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
        }

        /// <summary>
        /// Sends a notification to the web service if a device is missing
        /// </summary>
        /// <param name="deviceID">ID of the missing device</param>
        /// <param name="userID">ID of the user of the missing device</param>
        /// <param name="sensorID">ID of the sensor of the missing device</param>
        /// <returns>Returns true if the notification is successful, or false if failed</returns>
        public bool NotifyMissingDevice(string deviceID, string userID, string sensorID)  
        {
            try
            {
                string jsonData = NotificationJSON(deviceID, userID, sensorID);
                Debug.Print(jsonData);

                HttpWebRequest request = CreateRequest(
                    "Services/DataLogging.svc/notification");
                request.Method = "POST";
                request.ContentLength = jsonData.Length;

                using (HttpWebResponse response = GetResponse(request))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
        }

        // Creates a json string for sending notification about which device is missing
        private string NotificationJSON(string deviceID, string userID, string sensorID)
        {
            return "{\"deviceId\":" + deviceID + ",\"sensorId\":" + sensorID +
                ",\"reason\":\"lost\",\"userId\":" + userID + "}";
        }

        /// <summary>
        /// Sends heart-beat messages to the web service to announce that the device is still working
        /// </summary>
        /// <returns>Returns true if the notification is successful, or false if failed</returns>
        public bool HeartBeatMessage()
        {
            try
            {
                HttpWebRequest request = CreateRequest(
                    "Services/device.svc/heartbeat?deviceid=" + Device.Id);
                request.Method = "GET";
                request.ContentLength = 0;

                using (HttpWebResponse response = GetResponse(request))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
        }

        /// <summary>
        /// Sends the historic data which is stored in a specified file to the web service 
        /// </summary>
        /// <param name="filePath">Path of the file which stores the historic data</param>
        /// <param name="firstTime">Timestamp of the first entry in the file</param>
        /// <param name="lastTime">Timestamp of the last entry in the file</param>
        /// <param name="sensorName">Name of the sensor which generates the data</param>
        /// <returns>Returns true if data sending is successful, or false if failed</returns>
        public bool SendHistoricData(string filePath, string firstTime, 
            string lastTime, string sensorName)
        {
            try
            {
                using (StreamReader sr = new StreamReader(filePath))
                {
                    string jsonData = DataJSON(sr.ReadToEnd(), firstTime, lastTime, sensorName);
                    Debug.Print(jsonData);

                    HttpWebRequest request = CreateRequest(
                        "Services/DataLogging.svc/logData");
                    request.Method = "POST";
                    request.ContentLength = jsonData.Length;
                    request.ContentType = "application/octet-stream";

                    using (HttpWebResponse response = GetResponse(request, jsonData))
                    {
                        using (StreamReader streamReader =
                            new StreamReader(response.GetResponseStream()))
                        {
                            if ((response.StatusCode == HttpStatusCode.OK)
                                && streamReader.ReadToEnd().Equals(RESULT))
                            {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
            catch (IOException ie)
            {
                Debug.Print(ie.Message);
                return false;
            }
            catch (ArgumentException ae)
            {
                Debug.Print("Sensor does not exist");
                Debug.Print(ae.Message);
                return false;
            }
        }

        // Retrieves the sensor ID based on given sensor name
        private string RetrieveID(string sensorName)
        {
            foreach (Sensor s in Sensors)
            {
                if (s.Name.Equals(sensorName))
                {
                    return s.Id;
                }
            }
            throw new ArgumentException();
        }

        // Retrieves the sensor type based on given sensor name
        private SensorType RetrieveType(string sensorName)
        {
            foreach (Sensor s in Sensors)
            {
                if (s.Name.Equals(sensorName))
                {
                    return s.Type;
                }
            }
            throw new ArgumentException();
        }

        /// <summary>
        /// Sends the live data gathered from the sensors to the web service
        /// </summary>
        /// <param name="sensorData">An Arraylist of the TimedData objects which contain live data gathered from the sensors</param>
        /// <param name="firstTime">Timestamp of the first entry of the live data</param>
        /// <param name="lastTime">Timestamp of the last entry of the live data</param>
        /// <param name="sensorName">Name of the sensor which generates real time data</param>
        /// <returns>Returns true if data sending is successful, or false if failed</returns>
        public bool SendCurrentData(ArrayList sensorData, string firstTime, 
            string lastTime, string sensorName)
        {
            try
            {
                string jsonData = string.Empty;
                switch (this.RetrieveType(sensorName))
                {
                    case SensorType.temperatureHumidity:
                        jsonData = DataJSON(TemperatureArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.gps:
                        jsonData = DataJSON(GPSArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.accelerometer:
                        jsonData = DataJSON(AcceleroArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.potentiometer:
                        jsonData = DataJSON(PotentioArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.barometer:
                        jsonData = DataJSON(BarometerArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.color:
                        jsonData = DataJSON(ColorArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.compass:
                        jsonData = DataJSON(CompassArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.current:
                        jsonData = DataJSON(CurrentArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.distance:
                        jsonData = DataJSON(DistanceArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.gas:
                        jsonData = DataJSON(GasArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.gyro:
                        jsonData = DataJSON(GyroArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.light:
                        jsonData = DataJSON(LightArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.moisture:
                        jsonData = DataJSON(MoistureArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.pulse:
                        jsonData = DataJSON(PulseArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;

                    case SensorType.thermocouple:
                        jsonData = DataJSON(ThermocoupleArray(sensorData), firstTime, lastTime, sensorName);
                        Debug.Print(jsonData);
                        break;
                }

                HttpWebRequest request = CreateRequest(
                    "Services/DataLogging.svc/logData");
                request.Method = "POST";
                request.ContentLength = jsonData.Length;
                request.ContentType = "application/octet-stream";

                using (HttpWebResponse response = GetResponse(request, jsonData))
                {
                    using (StreamReader streamReader =
                        new StreamReader(response.GetResponseStream()))
                    {
                        if ((response.StatusCode == HttpStatusCode.OK)
                            && streamReader.ReadToEnd().Equals(RESULT))
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch (WebException we)
            {
                Debug.Print(we.Message);
                return false;
            }
            catch (ArgumentException ae)
            {
                Debug.Print("Sensor does not exist");
                Debug.Print(ae.Message);
                return false;
            }
        }

        // Creates a json string for sending sensor-derived data
        private string DataJSON(string jsonData, string firstTime, string lastTime, string sensorName)
        {
            string json = "{\"deviceId\":" + Device.Id
                + ",\"sensorId\":" + this.RetrieveID(sensorName)
                + ",\"firstTime\":\"" + firstTime
                + "\",\"lastTime\":\"" + lastTime
                + "\",\"data\":[";
            json += jsonData;
            json = json.Substring(0, json.Length - 1);
            json += "]}";
            return json;
        }

        // Creates a json array for Temperature and Humidity Modules 
        private string TemperatureArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"temperature\":" + td.FirstData
                    + ",\"humidity\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for GPS Modules
        private string GPSArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"latitude\":" + td.FirstData
                    + ",\"longitude\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Accelerometer Modules
        private string AcceleroArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"x\":" + td.FirstData
                    + ",\"y\":" + td.SecondData
                    + ",\"z\":" + td.ThirdData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Potentiometer Modules
        private string PotentioArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"percentage\":" + td.FirstData
                    + ",\"voltage\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Barometer Modules
        private string BarometerArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"pressure\":" + td.FirstData
                    + ",\"temperature\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for ColorSense Modules
        private string ColorArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"blue\":" + td.FirstData
                    + ",\"green\":" + td.SecondData
                    + ",\"red\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Compass Modules
        private string CompassArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"angle\":" + td.FirstData
                    + ",\"x\":" + td.SecondData
                    + ",\"y\":\"" + td.ThirdData
                    + ",\"z\":" + td.FourthData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Current Modules
        private string CurrentArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"current\":" + td.FirstData
                    + ",\"peaking\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Distance Modules
        private string DistanceArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"distance\":" + td.FirstData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Gas Modules
        private string GasArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"voltage\":" + td.FirstData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Gyro Modules
        private string GyroArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"temperature\":" + td.FirstData
                    + ",\"x\":" + td.SecondData
                    + ",\"y\":\"" + td.ThirdData
                    + ",\"z\":" + td.FourthData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Light Modules
        private string LightArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"strength\":" + td.FirstData
                    + ",\"voltage\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Moisture Modules
        private string MoistureArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"moisture\":" + td.FirstData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Pulse Modules
        private string PulseArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"rate\":" + td.FirstData
                    + ",\"strength\":" + td.SecondData
                    + ",\"oxygenSaturation\":" + td.ThirdData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        // Creates a json array for Thermocouple Modules
        private string ThermocoupleArray(ArrayList sensorData)
        {
            string json = string.Empty;
            foreach (TimedData td in sensorData)
            {
                json += "{\"externalTemperature\":" + td.FirstData
                    + ",\"internalTemperature\":" + td.SecondData
                    + ",\"time\":\"" + td.DataTime
                    + "\"},";
            }
            return json;
        }

        /// <summary>
        /// Deletes the specified file 
        /// </summary>
        /// <param name="filePath">Path of the file to be deleted</param>
        public void DeleteFile(string filePath)
        {
            try
            {
                File.Delete(filePath);
            }
            catch (IOException ie)
            {
                Debug.Print(ie.Message);
            }
        }

        /// <summary>
        /// Appends the data gathered from the sensors to the specified file
        /// </summary>
        /// <param name="filePath">Path of the specified file which stores the data</param>
        /// <param name="sensorData">An ArrayList of TimedData objects contains data 
        /// gathered from the sensor to be appended</param>
        public void AppendFile(string filePath, ArrayList sensorData)
        {
            try
            {
                string jsonData = TemperatureArray(sensorData);
                Debug.Print(jsonData);

                StreamWriter textFile = new StreamWriter(filePath, true);

                textFile.Write(jsonData);
                textFile.Flush();
                textFile.Close();
            }
            catch (IOException ie)
            {
                Debug.Print(ie.Message);
            }
        }
    }
}
